﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using CloudShot.Editor;
using CloudShot.Editor.CanvasFigures;
using CloudShot.Properties;
using CloudShot.Utils;
using CloudShot.Utils.Tweens;

namespace CloudShot.ViewModels
{
  public class PropertiesFormViewModel
  {
    private readonly Animator _animator;
    private readonly List<FigurePropViewModel> _figureProps = new List<FigurePropViewModel>();

    //General Properties
    public string Name { get; private set; }

    private Color? _borderColor;
    public Color? BorderColor
    {
      get { return _borderColor; }
      set
      {
        _borderColor = value;
        SetModelProperty<CanvasObject, Color>(p => p.BorderColor, v => v.BorderColor, value, AnimateColor);
      }
    }

    private int? _borderWidth;
    public int? BorderWidth
    {
      get { return _borderWidth; }
      set
      {
        _borderWidth = value;
        SetModelProperty<CanvasObject, int>(p => p.BorderWidth, v => v.BorderWidth, value, AnimateInt);
      }
    }

    private LineType? _lineType;
    public LineType? LineType
    {
      get { return _lineType; }
      set
      {
        _lineType = value;
        SetModelProperty<CanvasObject, LineType>(p => p.LineType, p => p.LineType, value);
      }
    }

    //Arrow
    public bool IsArrowPartVisible
    {
      get { return _figureProps.All(p => p.IsArrowPartVisible); }
    }

    private bool? _isArrowAtStart;
    public bool? IsArrowAtStart
    {
      get { return _isArrowAtStart; }
      set
      {
        _isArrowAtStart = value;
        SetModelProperty<CanvasArrow, bool>(p => p.IsArrowAtStart, p => p.IsArrowAtStart, value);
      }
    }

    private bool? _isArrowAtEnd;
    public bool? IsArrowAtEnd
    {
      get { return _isArrowAtEnd; }
      set
      {
        _isArrowAtEnd = value;
        SetModelProperty<CanvasArrow, bool>(p => p.IsArrowAtEnd, p => p.IsArrowAtEnd, value);
      }
    }

    //Back color
    public bool IsBackColorPartVisible
    {
      get { return _figureProps.All(p => p.IsBackColorPartVisible); }
    }

    public bool IsAddTextPartVisible
    {
      //disable in multiselect
      get { return _figureProps.Count == 1 && _figureProps.All(p => p.IsAddTextPartVisible); }
    }

    private Color? _backColor;
    public Color? BackColor
    {
      get { return _backColor; }
      set
      {
        _backColor = value;
        SetModelProperty<CanvasRectangleBase, Color>(p => p.BackColor, v => v.BackColor, value, AnimateColor);
      }
    }

    private byte? _opacity;
    public byte? BackgroundOpacity
    {
      get { return _opacity; }
      set
      {
        _opacity = value;
        SetModelProperty<CanvasRectangleBase, byte>(p => p.BackgroundOpacity, p => p.BackgroundOpacity, value, AnimateByte);
      }
    }

    //Text
    public bool IsTextPartVisible
    {
      get { return _figureProps.All(p => p.IsTextPartVisible); }
    }

    private float? _fontSize;
    public float? FontSize
    {
      get { return _fontSize; }
      set
      {
        _fontSize = value;
        SetModelProperty<CanvasText, float>(p => p.FontSize, p => p.FontSize, value);
      }
    }

    private Color? _textColor;
    public Color? TextColor
    {
      get { return _textColor; }
      set
      {
        _textColor = value;
        SetModelProperty<CanvasText, Color>(p => p.TextColor, v => v.TextColor, value, AnimateColor);
      }
    }

    private bool? _isItalic;
    public bool? IsItalic
    {
      get { return _isItalic; }
      set
      {
        _isItalic = value;
        SetModelProperty<CanvasText, bool>(p => p.IsItalic, p => p.IsItalic, value);
      }
    }

    private bool? _isBold;
    public bool? IsBold
    {
      get { return _isBold; }
      set
      {
        _isBold = value;
        SetModelProperty<CanvasText, bool>(p => p.IsBold, p => p.IsBold, value);
      }
    }

    private bool? _isUnderline;
    public bool? IsUnderline
    {
      get { return _isUnderline; }
      set
      {
        _isUnderline = value;
        SetModelProperty<CanvasText, bool>(p => p.IsUnderline, p => p.IsUnderline, value);
      }
    }

    private StringAlignment? _textAlignment;
    public StringAlignment? TextAlignment
    {
      get { return _textAlignment; }
      set
      {
        _textAlignment = value;
        SetModelProperty<CanvasText, StringAlignment>(p => p.TextAlignment, p => p.TextAlignment, value);
      }
    }

    public bool CanChangeFontSizeByButtons { get; private set; }

    public PropertiesFormViewModel(IEnumerable<CanvasObject> canvasObjects, Animator animator)
    {
      _animator = animator;
      _figureProps.AddRange(canvasObjects.ToList().Select(o => new FigurePropViewModel(o)));

      //NOTE: init all back fields here
      _opacity = GetCommonProp<CanvasRectangleBase, byte>(o => o.BackgroundOpacity);
      _textColor = GetCommonProp<CanvasText, Color>(p => p.TextColor);
      _backColor = GetCommonProp<CanvasRectangleBase, Color>(p => p.BackColor);
      _borderWidth = GetCommonProp<CanvasObject, int>(o => o.BorderWidth);
      _borderColor = GetCommonProp<CanvasObject, Color>(p => p.BorderColor);
      _lineType = GetCommonProp<CanvasObject, LineType>(p => p.LineType);
      _isArrowAtStart = GetCommonProp<CanvasArrow, bool>(p => p.IsArrowAtStart);
      _isArrowAtEnd = GetCommonProp<CanvasArrow, bool>(p => p.IsArrowAtEnd);
      _fontSize = GetCommonProp<CanvasText, float>(p => p.FontSize);
      _isItalic = GetCommonProp<CanvasText, bool>(p => p.IsItalic);
      _isBold = GetCommonProp<CanvasText, bool>(p => p.IsBold);
      _isUnderline = GetCommonProp<CanvasText, bool>(p => p.IsUnderline);
      _textAlignment = GetCommonProp<CanvasText, StringAlignment>(p => p.TextAlignment);

      CanChangeFontSizeByButtons = _figureProps.Count == 1 && (_figureProps.Single().CanvasObject is CanvasText);
      Name = Resources.PropertyWindow_Title_GeneralFigure;

      if (AreAllOfType<CanvasText>())
        Name = Resources.PropertyWindow_Title_Text;
      else if (AreAllOfType<CanvasArrow>())
        Name = Resources.PropertyWindow_Title_ArrowLine;
      else if (AreAllOfType<CanvasEllipse>())
        Name = Resources.PropertyWindow_Title_Ellipse;
      else if (AreAllOfType<CanvasRectangle>())
        Name = Resources.PropertyWindow_Title_Rectangle;

      if (_figureProps.Count > 1)
        Name += "s";
    }

    public Rectangle GetBoundingBox()
    {
      return _figureProps.Select(f => f.CanvasObject).GetGroupBoundingBox();
    }

    private void AnimateByte<T>(Animator animator, T obj, Expression<Func<T, byte>> expression, byte value) where T : CanvasObject
    {
      animator.StartByteAnimation(obj, expression, value);
    }

    private void AnimateColor<T>(Animator animator, T obj, Expression<Func<T, Color>> expression, Color value) where T : CanvasObject
    {
      //Debug.WriteLine(string.Format("%%AnimateColor name {0}, to {1}", expression.GetPropertyInfo().Name, value));
      animator.StartColorAnimation(obj, expression, value);
    }

    private void AnimateInt<T>(Animator animator, T obj, Expression<Func<T, int>> expression, int value) where T : CanvasObject
    {
      animator.StartIntAnimation(obj, expression, value);
    }

    private bool AreAllOfType<TObject>() where TObject : CanvasObject
    {
      foreach (FigurePropViewModel prop in _figureProps)
      {
        if (!(prop.CanvasObject is TObject))
          return false;
      }

      return true;
    }

    /// <summary>
    /// If all values in the _figureProps are the same, returns this value; otherwise returns null
    /// </summary>
    private TValue? GetCommonProp<TObject, TValue>(Expression<Func<TObject, TValue>> expression)
      where TValue : struct
      where TObject : CanvasObject
    {
      TValue? previousValue = null; 

      foreach (var prop in _figureProps)
      {
        var canvasObject = prop.CanvasObject as TObject;
        if (canvasObject == null)
          continue;

        TValue value = expression.GetPropertyValue(canvasObject);

        if (previousValue == null)
        {
          previousValue = value;
        }
        else if (!value.Equals(previousValue.Value))
        {
          //we have different values in a property set
          return null;
        }
      }
      
      //all values in a property set is the same
      return previousValue;
    }

    private void SetModelProperty<T, TValue>(Expression<Func<T, TValue>> expression, Expression<Func<FigurePropViewModel, TValue>> viewExpression, TValue? value, Action<Animator, T, Expression<Func<T, TValue>>, TValue> animate = null)
      where T : CanvasObject
      where TValue : struct
    {
      if (!value.HasValue)
      {
        PropertyInfo propertyInfo = expression.GetPropertyInfo();
        PropertyInfo valuePropInfo = viewExpression.GetPropertyInfo();

        foreach (var propViewModel in _figureProps)
        {
          if (propViewModel.CanvasObject is T)
          {
            SetPropValue(expression, animate, propertyInfo, (T)propViewModel.CanvasObject, (TValue)valuePropInfo.GetValue(propViewModel));
          }
        }
      }
      else
      {
        SetProperty(expression, value, animate);
      }
    }

    private void SetProperty<TObject, TValue>(Expression<Func<TObject, TValue>> expression, TValue? value, Action<Animator, TObject, Expression<Func<TObject, TValue>>, TValue> animate = null)
      where TObject : CanvasObject
      where TValue : struct
    {
      if (!value.HasValue)
        return;

      PropertyInfo propertyInfo = expression.GetPropertyInfo();

      foreach (FigurePropViewModel prop in _figureProps)
      {
        var canvasObject = prop.CanvasObject as TObject;
        if (canvasObject == null)
          continue;

        SetPropValue(expression, animate, propertyInfo, canvasObject, value.Value);
      }
    }

    private void SetPropValue<TObject, TValue>(Expression<Func<TObject, TValue>> expression, Action<Animator, TObject, Expression<Func<TObject, TValue>>, TValue> animate, PropertyInfo propertyInfo, TObject canvasObject, TValue val)
      where TObject : CanvasObject
      where TValue : struct
    {
      if (animate == null)
        propertyInfo.SetValue(canvasObject, val, null);
      else
      {
        animate(_animator, canvasObject, expression, val);
      }
    }
  }
}